package edu.mercer.sse.tasks.math;

import java.util.concurrent.Callable;
import java.util.logging.Level;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import edu.mercer.sse.logging.CustomLogger;
import edu.mercer.sse.logging.LoggerCustom;
import edu.mercer.sse.tlocal.ThreadObjectProxification;
import edu.mercer.sse.tlocal.ThreadObjectProxy;

/**
 * 
 * Estimate Pi using Monte Carlo method.<p>
 *
 * Demonstrate thread-global object proxy usage. Pi calculation is stored in
 * 	thread-globals and accessed by multiple instances of this MonteCarloPi 
 * 	class.<p>
 * 
 * Multiple instances within the same thread: generally improve Pi quality, 
 * 	with each revising the previous calculation. Accomplished through use of 
 * 	the thread-global value from the previous calculation. Pi estimate 
 * 	<u>combined results of all tasks within the single thread</u>.<p>
 *
 * Multiple instances executed in different threads: decreased Pi quality,
 * 	with each thread using its own thread-globals for Pi. Calculation not 
 *  available to other threads; <u>thread-globals for Pi estimate are separate 
 *  for each thread</u>. Pi estimate <u>only as accurate as single thread 
 *  tasks' execution provides</u>.
 * <p>
 * 
 */

public class MonteCarloPi implements Callable<Double>
{
	/**
	 * Name prefix of thread-globals.<p>
	 *  
	 * Allows choice of non-cumulative, or cumulative Pi calculation. 
	 */
	
	private String _piJobName = "";
	
	@SuppressWarnings("unused")
	private final CustomLogger _log = LoggerCustom.obtainLog("MonteCarloPi",
			Level.ALL);
	
	private Double _tosses = 0d;
	
	
	public MonteCarloPi( final String piJobName, final Double tosses )
	{	
		checkNotNull( piJobName, "piJobName null" );
		checkNotNull( tosses   , "piJobName null" );

		checkArgument( piJobName.length() > 0, "piJobName empty" );
		checkArgument( tosses > 0            , "tosses zero" );
		
		_piJobName = piJobName;

		_tosses = tosses;

	}//constructor
	
	
	/**
	 * Calculate Monte Carlo Pi.<p>
	 * 
	 * Use previously gathered hits with its tosses parameter for Pi 
	 * 	calculation.<p>
	 * 
	 * @param hits - Double number of hits, i.e., successes
	 * 
	 * @param tosses - Double number of guesses; i.e., attempts
	 * 
	 * @return  Double Pi approximation
	 */
	
	private Double calculateMonteCarlo( final Double hits, 
										final Double tosses )
	{
		return ( (4 * hits) / tosses  );
		
	}//method
	
	
	/**
	 * Use thread-globals for Pi estimation.<p>
	 * 
	 * Cumulative results for all Pi estimation thread-global figures of 
	 * 	'_piJobName' prefix from constructor.<p>
	 */
	
	@Override
	public Double call()
	{
		final ThreadObjectProxification<String,Double> threadObjectProxy = 
				ThreadObjectProxy.factory();
		
		/* 
		 * add the number of tosses for this run into tosses thread-global 
		 * 	defined for this thread
		 * 
		 * must be done here, and not in constructor as this method may be 
		 * 	executed in another thread than constructor
		 */
		
		threadObjectProxy.append( _piJobName + "tosses", _tosses );
				
		/* only using one thread-global Double instance per jobname */

		/* store Pi into the thread-global, retrieve and return Pi */
				
		threadObjectProxy.append( _piJobName + "hits" , 
								  new EstimatePiFunction().apply(_tosses));
		
		/* 
		 * return cumulative calculation using thread-global pi estimate 
		 * values, incorporating every previous execution for this thread 
		 * into resulting Pi accuracy
		 */
		
		return calculateMonteCarlo( 
				threadObjectProxy.get( _piJobName + "hits" ), 
				threadObjectProxy.get( _piJobName + "tosses"));
		
	}//method
	
	
}//class
